package com.apsbyoptaplanner.score;

import com.apsbyoptaplanner.domain.Machine;
import com.apsbyoptaplanner.domain.Task;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;

import java.util.function.Function;

import static org.optaplanner.core.api.score.stream.ConstraintCollectors.sum;
import static org.optaplanner.core.api.score.stream.Joiners.equal;

public final class TaskConstraintProvider implements ConstraintProvider {
    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[] {
                YarnTypeMatch(constraintFactory),
                MachineCapacity(constraintFactory),
                MachineCost(constraintFactory)
        };
    }

    //任务机台类型必须与机台类型匹配，否则违反硬约束
    private Constraint YarnTypeMatch(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(Task.class)
                .filter(task->task.getRequiredYarnType() != task.getMachine().getYarnType())
                .penalize(HardSoftScore.ONE_HARD, Task::getRequiredYarnType)
                .asConstraint("YarnTypeMatch");
    }

    //机台加工的所有任务的数量和不能超过机台的加工能力
    private Constraint MachineCapacity(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(Task.class)
                .groupBy(Task::getMachine, sum(Task::getAmount))
                .filter((machine, amount) -> amount > machine.getCapacity())
                .penalize(HardSoftScore.ONE_HARD,
                        (machine, amount) -> amount - machine.getCapacity())
                .asConstraint("MachineCapacity");
    }

    //所有机台加起来消耗最小
    private Constraint MachineCost(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(Machine.class)
                .ifExists(Task.class, equal(Function.identity(), Task::getMachine))
                .penalize(HardSoftScore.ONE_SOFT, Machine::getCost)
                .asConstraint("MachineCost");
    }
}
